import { ExternalLink } from 'lucide-react'; import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { BreadcrumbLd, Breadcrumbs } from '@/components/meta/breadcrumb-ld'; import { Chip, ConfidenceBadge, EntityBadge, TierBadge } from '@/components/ui/badges'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { Hint } from '@/components/ui/hint'; import { Container, Note, PageHeader, Section } from '@/components/ui/section'; import { Unavailable } from '@/components/ui/unavailable'; import { ApiError, apiD3 } from '@/lib/api'; import { fmtDateTime, fmtInt, fmtValue, hostOf } from '@/lib/format'; import { propertyLabel, routes, SITE_NAME } from '@/lib/site'; import type { ClaimDetail, ClaimRow } from '@/lib/types'; type Params = { params: Promise<{ id: string }> }; const STATUS_TONE: Record = { current: 'text-positive bg-positive-soft', superseded: 'text-ink-2 bg-surface-2', conflicting: 'text-danger bg-danger-soft', retracted: 'text-warning bg-warning-soft' }; async function loadClaim(id: string): Promise { try { return await apiD3.claim(id); } catch (e) { if (e instanceof ApiError && e.notFound) notFound(); return null; } } export async function generateMetadata({ params }: Params): Promise { const { id } = await params; let c: ClaimDetail | null = null; try { c = await apiD3.claim(id); } catch { c = null; } const title = c ? `${c.entity?.name ?? 'Entity'} · ${propertyLabel(c.property)} = ${fmtValue(c.claim.value, c.property)} — claim` : 'Claim'; return { title, description: 'One temporal claim of the AI Atlas graph: value, source, tier, extractor, validity interval and its lifecycle (previous, superseding and conflicting claims).', alternates: { canonical: routes.claim(id) }, robots: { index: false, follow: true } }; } function StatusChip({ s }: { s: string }) { return {s}; } function ClaimTable({ rows, property, currentId, caption }: { rows: ClaimRow[]; property: string; currentId: string; caption: string }) { return ( Value Status Valid from Valid to Source Tier Extractor Claim {rows.length === 0 && None.} {rows.map((c) => ( {fmtValue(c.value, property)}{c.unit ? {c.unit} : null} {fmtDateTime(c.valid_from)} {c.valid_to ? fmtDateTime(c.valid_to) : open} {c.source_url ? ( {c.source_name ?? hostOf(c.source_url) ?? 'source'} ) : ( — )} {c.extractor} {c.id === currentId ? this claim : {c.id}} ))} ); } export default async function ClaimPage({ params }: Params) { const { id } = await params; const c = await loadClaim(id); if (!c) { return (
); } const cl = c.claim; const e = c.entity; const crumbs = [{ name: SITE_NAME, href: '/' }, ...(e ? [{ name: e.name, href: routes.entity(e) }, { name: 'History', href: routes.entityHistory(e, c.property) }] : []), { name: `Claim · ${propertyLabel(c.property)}`, href: routes.claim(id) }]; const chain = c.chain ?? { previous: [], superseding: [], conflicting: [], history_count: 0 }; const lifecycle: ClaimRow[] = [...chain.previous, cl, ...chain.superseding].filter((x, i, arr) => arr.findIndex((y) => y.id === x.id) === i).sort((a, b) => (a.valid_from < b.valid_from ? -1 : 1)); return ( Claim {e && } } title={ <> {e ? ( {e.name} ) : ( 'Entity' )}{' '} · {propertyLabel(c.property)} = {fmtValue(cl.value, c.property)} {cl.unit && {cl.unit}} } lede="A temporal claim: who said it, when it was observed, how it was extracted, how long it has been current, and what came before and after it. Claims are never overwritten — a better source supersedes, a worse one conflicts." aside={

{cl.id}

} />
Lifecycle {fmtInt(chain.history_count)} in history} lede="Previous values, this claim, and the claims that superseded it — in validity order." hairline={false} className="pt-0">
Conflicting claims {fmtInt(chain.conflicting.length)}} lede="Values stated by other sources that disagree with the current one. Stored side by side, flagged for review, never averaged.">
{c.note && {c.note}}
); }